# -*- coding: utf-8 -*-
# @Time    : 2024/6/12 10:56
# @Author  : yujiahao
# @File    : 15_function_namespaces_nesting.py
# @description: Python函数命名空间和嵌套

"""
在python解释器开始执行之后, 就会在内存中开辟一个空间, 每当遇到一个变量的时候, 就把变量名和值之间的关系记录下来,
但是当遇到函数定义的时候, 解释器只是把函数名读入内存, 表示这个函数存在了, 至于函数内部的变量和逻辑, 解释器是不关心的。
也就是说一开始的时候函数只是加载进来, 仅此而已, 只有当函数被调用和访问的时候, 解释器才会根据函数内部声明的变量来进行开辟变量的内部空间。
随着函数执行完毕, 这些函数内部变量占用的空间也会随着函数执行完毕而被清空。

def fun():
    a = 10
    print(a)
fun()
print(a)    # a不存在了已经..

Java、Scala 和 Python 的命名空间详解

一、Java 的命名空间：
1. 包（Package）：
   - Java 使用包来组织类和接口，包名提供了命名空间。
   - 包名通常使用反向域名系统（如 com.example.project），以避免命名冲突。
   - 使用 `package` 关键字定义包，使用 `import` 关键字导入包中的类或接口。
   - 包与文件系统目录结构对应，包名与目录结构一致。
   - 示例：
     ```java
     package com.example.project;
     import com.example.project.utils.Helper;
     ```

2. 类（Class）和接口（Interface）：
   - 类和接口本身也是命名空间，包含类变量、实例变量、方法和嵌套类等。
   - 可以使用完全限定名（如 `com.example.project.ClassName`）来避免命名冲突。
   - 静态成员属于类命名空间，实例成员属于对象命名空间。
   - 嵌套类（静态内部类）和内部类（非静态内部类）也有各自的命名空间。
   - 示例：
     ```java
     public class OuterClass {
         private static String staticVar = "Static Variable";
         private String instanceVar = "Instance Variable";

         public static class StaticNestedClass {
             public void display() {
                 System.out.println(staticVar);
             }
         }

         public class InnerClass {
             public void display() {
                 System.out.println(instanceVar);
             }
         }
     }
     ```

3. 方法（Method）：
   - 方法内部的局部变量和参数属于方法的命名空间。
   - 局部变量和参数在方法执行时创建，方法返回后销毁。
   - 方法内部可以访问类的成员变量和方法。
   - 示例：
     ```java
     public void exampleMethod(int param) {
         int localVar = 10;
         System.out.println(localVar + param);
     }
     ```

4. 块（Block）：
   - 代码块（如 if, for, while 等）内部的变量也有自己的命名空间。
   - 这些变量在块执行时创建，块执行完毕后销毁。
   - 示例：
     ```java
     if (true) {
         int blockVar = 5;
         System.out.println(blockVar);
     }
     ```

二、Scala 的命名空间：
1. 包（Package）：
   - Scala 也使用包来组织代码，包名提供了命名空间。
   - 包可以嵌套，使用 `package` 关键字定义包。
   - 可以使用 `import` 关键字导入包中的类、对象和特质（trait）。
   - Scala 支持在同一个文件中定义多个包。
   - 示例：
     ```scala
     package com.example.project

     package utils {
         object Helper {
             def help() = println("Helping")
         }
     }

     import com.example.project.utils.Helper
     ```

2. 对象（Object）和类（Class）：
   - Scala 中的对象（单例对象）和类也是命名空间，包含成员变量、方法和嵌套类等。
   - 对象和类的成员可以通过对象名或类名访问。
   - 伴生对象和伴生类共享同一个命名空间，可以互相访问私有成员。
   - Scala 中的 `object` 是一种特殊的类，只有一个实例。
   - 示例：
     ```scala
     object Singleton {
         private var count = 0

         def increment() = {
             count += 1
         }

         def getCount = count
     }

     class MyClass {
         def show() = println("Class Method")
     }

     object MyClass {
         def apply() = new MyClass
     }
     ```

3. 方法（Method）：
   - 方法内部的局部变量和参数属于方法的命名空间。
   - 局部变量和参数在方法执行时创建，方法返回后销毁。
   - 方法内部可以访问类的成员变量和方法。
   - 示例：
     ```scala
     def exampleMethod(param: Int): Unit = {
         val localVar = 10
         println(localVar + param)
     }
     ```

4. 块（Block）：
   - 代码块（如 if, for, while 等）内部的变量也有自己的命名空间。
   - 这些变量在块执行时创建，块执行完毕后销毁。
   - 示例：
     ```scala
     if (true) {
         val blockVar = 5
         println(blockVar)
     }
     ```

三、Python 的命名空间：

按照加载顺序：

1. 内置命名空间（Built-in Namespace）： 存放python解释器为我们提供的名字, list, tuple, str, int这些都是内置命名空间　
   - 包含 Python 解释器启动时加载的所有内置标识符（如 `print`, `len` 等）。
   - 这些标识符可以在任何地方使用。
   - 可以通过 `builtins` 模块访问这些标识符。
   - 示例：
     ```python
     print(len("Hello"))
     ```

2. 全局命名空间（Global Namespace）：我们直接在py文件中, 函数外声明的变量都属于全局命名空间
   - 包含在模块级别定义的所有标识符。
   - 每个模块都有自己的全局命名空间。
   - 在一个模块中定义的变量、函数和类都属于这个模块的全局命名空间。
   - 示例：
     ```python
     global_var = 10

     def global_function():
         print(global_var)
     ```

3. 局部命名空间（Local Namespace）：(函数被执行的时候) 在函数中声明的变量会放在局部命名空间
   - 包含在函数或方法中定义的所有标识符。
   - 每次调用函数时都会创建一个新的局部命名空间。
   - 当函数返回时，局部命名空间会被销毁。
   - 示例：
     ```python
     def example_function(param):
         local_var = 10
         print(local_var + param)
     ```

4. 闭包命名空间（Enclosing Namespace）：
   - 包含在嵌套函数中定义的所有标识符。
   - 如果函数嵌套在另一个函数中，则外层函数的局部命名空间被称为闭包命名空间。
   - 这种命名空间在嵌套函数中非常有用。
   - 示例：
     ```python
     def outer_function():
         outer_var = 10

         def inner_function():
             print(outer_var)

         inner_function()
     ```

按照取值顺序：

1. 局部命名空间
2. 闭包命名空间
3. 全局命名空间
4. 内置命名空间

四、命名空间的生命周期：
- 内置命名空间：在解释器启动时创建，并在解释器退出时销毁。
- 全局命名空间：在模块被导入时创建，并在模块被删除或解释器退出时销毁。
- 局部命名空间：在函数调用时创建，并在函数返回或抛出异常时销毁。
- 闭包命名空间：在外层函数调用时创建，并在外层函数返回后仍然存在，直到没有任何对它的引用为止。

五、命名空间的作用域（Python 特有）：
1. 局部作用域（Local Scope）：当前函数内的命名空间。
2. 闭包作用域（Enclosing Scope）：外层嵌套函数的命名空间。
3. 全局作用域（Global Scope）：当前模块的命名空间。
4. 内置作用域（Built-in Scope）：包含所有内置标识符的命名空间。

Python 使用 LEGB（Local, Enclosing, Global, Built-in）规则来查找变量：
- 首先查找局部命名空间。
- 如果在局部命名空间中找不到，则查找闭包命名空间。
- 如果在闭包命名空间中找不到，则查找全局命名空间。
- 如果在全局命名空间中找不到，则查找内置命名空间。

理解命名空间和作用域是编写清晰、易于维护的代码的关键。它有助于避免变量名冲突，并使代码更具可读性。通过遵循各自语言的命名空间规则，可以有效地组织和管理代码。
"""


"""
函数嵌套是指在一个函数内部定义另一个函数。这种方式在某些情况下非常有用，例如封装逻辑、创建闭包、实现递归等。下面我们详细说明 Java、Scala 和 Python 中的函数嵌套。

Java

在 Java 中，嵌套函数（即局部函数）是不被直接支持的。不过，可以通过在方法内部定义匿名类或使用 lambda 表达式来实现类似功能。

示例：
public class NestedFunctionExample {
    public static void main(String[] args) {
        // 外部方法
        outerMethod();
    }

    public static void outerMethod() {
        // 定义一个匿名类，模拟嵌套函数
        class InnerClass {
            void innerMethod() {
                System.out.println("This is an inner method");
            }
        }

        InnerClass inner = new InnerClass();
        inner.innerMethod();
    }
}

注意：Java 8 及以上版本可以使用 lambda 表达式来简化某些情况下的嵌套函数实现。例如：

import java.util.function.Consumer;

public class LambdaNestedFunctionExample {
    public static void main(String[] args) {
        // 外部方法
        outerMethod();
    }

    public static void outerMethod() {
        // 使用 lambda 表达式模拟嵌套函数
        Consumer<String> innerFunction = (message) -> {
            System.out.println("Inner function says: " + message);
        };

        // 调用嵌套函数
        innerFunction.accept("Hello from inner function");
    }
}

Scala

Scala 支持在函数内部定义嵌套函数。这使得 Scala 在处理闭包和局部作用域时非常灵活。

示例：
object NestedFunctionExample {
    def main(args: Array[String]): Unit = {
        // 调用外部函数
        outerFunction()
    }

    def outerFunction(): Unit = {
        // 嵌套函数
        def innerFunction(message: String): Unit = {
            println(s"Inner function says: $message")
        }

        // 调用嵌套函数
        innerFunction("Hello from inner function")
    }
}

Scala 中的嵌套函数可以访问外部函数的变量和参数，这使得它们非常适合用于闭包和递归。

示例：
object ClosureExample {
    def main(args: Array[String]): Unit = {
        // 调用外部函数
        outerFunction("Scala")
    }

    def outerFunction(language: String): Unit = {
        // 嵌套函数
        def innerFunction(): Unit = {
            println(s"Programming in $language is fun!")
        }

        // 调用嵌套函数
        innerFunction()
    }
}

Python

Python 也支持在函数内部定义嵌套函数。这种特性在创建闭包和封装逻辑时非常有用。

示例：
def outer_function():
    # 嵌套函数
    def inner_function(message):
        print(f"Inner function says: {message}")

    # 调用嵌套函数
    inner_function("Hello from inner function")

if __name__ == "__main__":
    # 调用外部函数
    outer_function()

Python 中的嵌套函数也可以访问外部函数的变量和参数，这使得它们非常适合用于闭包和递归。

示例：
def outer_function(language):
    # 嵌套函数
    def inner_function():
        print(f"Programming in {language} is fun!")

    # 调用嵌套函数
    inner_function()

if __name__ == "__main__":
    # 调用外部函数
    outer_function("Python")

总结：
1. Java 不直接支持嵌套函数，但可以通过匿名类或 lambda 表达式实现类似功能。匿名类适合复杂的嵌套逻辑，而 lambda 表达式适合简单的函数封装。
2. Scala 直接支持嵌套函数，语法简洁，适用于闭包和局部作用域的处理。嵌套函数可以访问外部函数的变量和参数，非常适合用于递归和闭包。
3. Python 也直接支持嵌套函数，常用于创建闭包和封装逻辑。嵌套函数可以访问外部函数的变量和参数，非常适合用于递归和闭包。

嵌套函数在不同语言中的实现方式各有不同，但它们都提供了在局部作用域内定义和调用函数的能力，这对于代码的组织和逻辑封装非常有用。
"""


# todo 一、命名空间和作用域

# 示例代码

# 全局命名空间
global_var = "I am a global variable"


def outer_function():
    # 闭包命名空间
    outer_var = "I am an outer variable"

    def inner_function():
        # 局部命名空间
        local_var = "I am a local variable"

        print(local_var)  # 局部变量
        print(outer_var)  # 闭包变量
        print(global_var)  # 全局变量
        print(globals())  # 打印全局作用域中的内容
        print(locals())  # 打印当前作用域中的内容

    inner_function()


outer_function()

# 尝试访问局部变量（会报错）
# print(local_var)  # NameError: name 'local_var' is not defined

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')


# todo 二、函数的嵌套

'''只要遇见了()就是函数的调用. 如果没有()就不是函数的调用函数的执行顺序，函数没有调用就不执行，明白下面的执行顺序很重要'''


def fun2():  # 1
    print(222)  # 4

    def fun3():  # 5
        print(666)  # 8

    print(444)  # 6
    fun3()  # 7
    print(888)  # 9


print(333)  # 2
fun2()  # 3
print(555)  # 10


print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')


# todo 三、gloabal、nonlocal
'''
global 关键字用于在函数内部声明一个变量为全局变量，从而可以在函数内部修改全局变量。
nonlocal 关键字用于在嵌套函数中声明一个变量为最近的外层函数中的局部变量，从而可以在内层函数中修改外层函数的局部变量。
'''

# 3.1、global

'''
首先我们写这样一个代码, 首先在全局声明一个变量, 然后再局部调用这个变量, 并改变这 个变量的值.

在这个示例中，global x 声明我们在 outer_function 中使用的 x 是全局变量 x。
因此，当我们在 outer_function 中修改 x 的值时，实际上是修改全局变量 x。
'''

a = 100
def func():
    global a    # 加了个global表示不再局部创建这个变量了. 而是直接使用全局的a
    a = 28
print(a)
func()
print(a)




# 4.2、nonlocal


# ① 基本用法
'''
假设我们有一个外层函数 outer_function 和一个嵌套在其中的内层函数 inner_function。
我们希望在 inner_function 中修改 outer_function 中的局部变量 x，这时我们就可以使用 nonlocal 关键字。

在这个示例中：

    - x 在 outer_function 中被定义为局部变量。
    - 在 inner_function 中，使用 nonlocal x 声明 x 是 outer_function 中的局部变量。
    - 通过 inner_function 修改 x 的值，outer_function 中的 x 的值也被修改。
'''

def outer_function():
    x = 10  # 外层函数的局部变量
    def inner_function():
        nonlocal x
        x = 20  # 修改外层函数的局部变量

    inner_function()
    print(x)  # 输出 20
print('基本用法\n')
outer_function()

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')


# ② 多层嵌套函数

''' 
nonlocal 关键字可以在多层嵌套函数中使用，但只能影响最近的外层函数中的局部变量。

    在这个示例中：
       - x 在 outer_function 和 middle_function 中分别被定义为局部变量。
       - 在 inner_function 中，使用 nonlocal x 声明 x 是 middle_function 中的局部变量。
       - 通过 inner_function 修改 x 的值，middle_function 中的 x 的值被修改，而 outer_function 中的 x 不受影响。
    注意事项：   
       - 作用范围：nonlocal 只能用于嵌套函数中，且只能修改最近的外层函数中的局部变量。
       - 变量必须已存在：在使用 nonlocal 声明变量之前，该变量必须已经在外层函数中定义。如果外层函数中不存在该变量，会引发 SyntaxError。
       
            def outer_function():
                def inner_function():
                    nonlocal x  # 错误：外层函数中不存在 x
                    x = 20
            inner_function()       
            outer_function()

'''


def outer_function():
    x = 10  # 外层函数的局部变量

    def middle_function():
        x = 20  # 中间层函数的局部变量

        def inner_function():
            nonlocal x  # 声明 x 为中间层函数的局部变量
            x = 30  # 修改中间层函数的局部变量

        inner_function()
        print(f"x in middle_function: {x}")  # 输出 30

    middle_function()
    print(f"x in outer_function: {x}")  # 输出 10

print('函数嵌套\n')
outer_function()


#③ nonlocal 与闭包
'''nonlocal 关键字在创建闭包时非常有用。闭包【后面详细看】是指内层函数引用了外层函数的局部变量，并且在外层函数的作用域结束后，内层函数依然能够访问这些变量。'''
def make_counter():
    count = 0  # 外层函数的局部变量

    def counter():
        nonlocal count  # 声明 count 为外层函数的局部变量
        count += 1
        return count

    return counter

counter_a = make_counter()
print(counter_a())  # 输出 1
print(counter_a())  # 输出 2

counter_b = make_counter()
print(counter_b())  # 输出 1



print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# 做个练习，下面这个弄明白就明白了 #步骤 => 输出值
a = 1 # 1
def fun_1():    # 2
    a = 2     # 5
    def fun_2():        # 6
        nonlocal a
        a = 3        # 9
        def fun_3():   # 10
            a = 4          # 13
            print(a)        # 14 => 4
        print(a)        # 11  => 3
        fun_3()        # 12
        print(a)  # 15 => 3
    print(a)    # 7 => 2
    fun_2()    # 8
    print(a)  # 16 => 3
print(a)   # 3 => 1
fun_1()       # 4
print(a) # 17 => 1


print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')


# todo 三、函数名的运用

'''函数名是一个变量, 但它是一个特殊的变量, 与括号配合可以执行函数的变量，函数的调用和函数对象的引用是有区别的。
    具体来说：
     - 引用函数对象：当你直接使用函数名（不带括号）时，你是在引用函数对象本身。
     - 调用函数：当你使用函数名并带上括号时，你是在调用这个函数。'''


# 1.1.函数名的内存地址
def func():
    print("秦人，集合")


print(func)  # 打印出了是一个函数的地址


# 1.2 函数名可以赋值给其他变量
def func():
    print("秦人，集合")
    print(func)


a = func  # 把函数当成一个值赋值给另一个变量

a()  # 函数调用 func()


# 1.3. 函数名可以当做容器类的元素
def func1():
    print("秦")


def func2():
    print("人")


def func3():
    print("集")


def func4():
    print("合")


lst = [func1, func2, func3, func4]
for i in lst:
    i()


# 1.4.函数名可以当做函数的参数
def func():
    print("吃了么")


def func2(fn):
    print("我是func2")
    fn()  # 执行传递过来的fn
    print("我是func2")


func2(func)  # 把函数func当成参数传递给func2的参数fn.


# 1.5. 函数名可以作为函数的返回值
def func_1():
    print("这里是函数1")

    def func_2():
        print("这里是函数2")

    print("这里是函数1")
    return func_2


fn = func_1()
# 执行函数1.  函数1返回的是函数2, 这时fn指向的就是上面函数2
fn()  # 执行func_2函数
